package com.techsoft.pool;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.support.nativejdbc.C3P0NativeJdbcExtractor;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.techsoft.ConnectionPool;
import com.techsoft.ConnectionPoolException;

public class C3P0ConnectionPool implements ConnectionPool {
	private static Logger Log = LoggerFactory
			.getLogger(C3P0ConnectionPool.class);

	private ComboPooledDataSource pds;
	private DatabaseType databaseType;
	private String driverName;
	private String jdbcURL;
	private String poolName = "inner";
	private String userName;
	private String password;
	private int minPool = 0;
	private int maxPool = 0;
	private int initPool = 0;

	@Override
	public Connection getConnection() throws ConnectionPoolException {
		Connection conn = null;
		try {
			conn = pds.getConnection();
			conn.setAutoCommit(false);
			Log.info("max pool size is " + pds.getMaxPoolSize());
			Log.info("min pool size is " + pds.getMinPoolSize());
			Log.info("busy pool size is " + pds.getNumBusyConnections());
		} catch (SQLException e) {
			try {
				if (pds.getNumBusyConnections() == pds.getMaxPoolSize()) {
					throw new ConnectionPoolException(MAXPOOLLIMIT);
				} else {
					throw new ConnectionPoolException(DATABASEERROR);
				}
			} catch (SQLException e1) {
				throw new ConnectionPoolException(e1.getMessage());
			}
		} catch (Exception e) {
			throw new ConnectionPoolException(e.getMessage());
		}

		return conn;
	}

	@Override
	public Connection getNativeConnection(Connection conn)
			throws ConnectionPoolException {
		try {
			C3P0NativeJdbcExtractor cp30NativeJdbcExtractor = new C3P0NativeJdbcExtractor();

			return cp30NativeJdbcExtractor.getNativeConnection(conn);
		} catch (Exception e) {
			throw new ConnectionPoolException(e.getMessage());
		}
	}

	@Override
	public void start() throws ConnectionPoolException {
		if (this.databaseType == null) {
			throw new ConnectionPoolException("Database type is not specified!");
		}

		if ((driverName == null) || (driverName.equals(""))) {
			throw new ConnectionPoolException("Not specify a database driver!");
		}

		if ((jdbcURL == null) || (jdbcURL.equals(""))) {
			throw new ConnectionPoolException(
					"Do not specify the database connection URL parameters!");
		}

		if ((poolName == null) || (poolName.equals(""))) {
			throw new ConnectionPoolException(
					"Do not specify the name of the connection pool!");
		}

		if ((userName == null) || (userName.equals(""))) {
			throw new ConnectionPoolException(
					"Do not specify the user name for the database!");
		}

		if ((password == null) || (password.equals(""))) {
			throw new ConnectionPoolException(
					"Do not specify the password for the database!");
		}

		if (minPool == 0) {
			throw new ConnectionPoolException(
					"Minimum number of connections can be set to zero!");
		}

		if (maxPool == 0) {
			throw new ConnectionPoolException(
					"Maximum number of connections can be set to zero!");
		}

		if (initPool == 0) {
			throw new ConnectionPoolException(
					"Initialize the number of connections can be set to zero!");
		}

		if (initPool > maxPool) {
			throw new ConnectionPoolException(
					"Initialize the number of connections can not be greater than the maximum number of connections!");
		}

		if (minPool > maxPool) {
			throw new ConnectionPoolException(
					"Minimum number of connections can not exceed the maximum number of connections!");
		}

		pds = new ComboPooledDataSource();
		try {
			pds.setDriverClass(driverName);
		} catch (PropertyVetoException e1) {
			throw new ConnectionPoolException(e1.getMessage());
		}
		pds.setJdbcUrl(jdbcURL);
		pds.setUser(userName);
		pds.setPassword(password);
		pds.setMaxPoolSize(maxPool);
		pds.setMinPoolSize(minPool);
		pds.setInitialPoolSize(initPool);
		pds.setDataSourceName(poolName);

		// 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数
		pds.setAcquireIncrement(3);
		// 定义在从数据库获取新连接失败后重复尝试的次数。Default: 30
		pds.setAcquireRetryAttempts(30);
		// 两次连接中间隔时间，单位毫秒。Default: 1000
		pds.setAcquireRetryDelay(200);
		// 获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效保留，
		// 并在下次调用getConnection()的时候继续尝试获取连接
		pds.setBreakAfterAcquireFailure(false);
		// 连接关闭时默认将所有未提交的操作回滚。Default: false
		pds.setAutoCommitOnClose(false);
		// c3p0将建一张名为C3P0TEST的空表，并使用其自带的查询语句进行测试
		// pds.setAutomaticTestTable("dual");
		pds.setPreferredTestQuery("select * from dual");
		// 当连接池用完时客户端调用getConnection()后等待获取新连接的时间，
		// 超时后将抛出SQLException,如设为0则无限期等待。单位毫秒。Default: 0
		pds.setCheckoutTimeout(10000);
		// 每1小时检查所有连接池中的空闲连接。Default: 0, 单位秒
		pds.setIdleConnectionTestPeriod(3600);
		// 最大空闲时间,24小时未使用则连接被丢弃。若为0则永不丢弃。Default: 0
		pds.setMaxIdleTime(86400);
		// c3p0是异步操作的，缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能
		// 通过多线程实现多个操作同时被执行。Default: 3
		pds.setNumHelperThreads(3);
		// 用户修改系统配置参数执行前最多等待300秒。Default: 300
		pds.setPropertyCycle(300);
		// 因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的
		// 时候都将校验其有效性, Default: false
		pds.setTestConnectionOnCheckout(false);
		// 如果设为true那么在取得连接的同时将校验连接的有效性。Default: false
		pds.setTestConnectionOnCheckin(false);
		// JDBC的标准参数，用以控制数据源内加载的PreparedStatements数量
		pds.setMaxStatements(1000);
		// JDBC的标准参数，用以控制数据源内每个连接加载的PreparedStatements数量
		pds.setMaxStatementsPerConnection(10);

		Connection conn = null;
		try {
			try {
				conn = pds.getConnection();
			} catch (Exception e) {
				throw new ConnectionPoolException(ConnectionPool.POOLSTARTERROR);
			}
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
				}
				conn = null;
			}
		}
	}

	@Override
	public void shutdown() throws ConnectionPoolException {
		pds.close();
		pds = null;
	}

	public DatabaseType getDatabaseType() {
		return databaseType;
	}

	public void setDatabaseType(DatabaseType databaseType) {
		this.databaseType = databaseType;
	}

	public String getDriverName() {
		return driverName;
	}

	public void setDriverName(String driverName) {
		this.driverName = driverName;
	}

	public String getJdbcURL() {
		return jdbcURL;
	}

	public void setJdbcURL(String jdbcURL) {
		this.jdbcURL = jdbcURL;
	}

	public String getPoolName() {
		return poolName;
	}

	public void setPoolName(String poolName) {
		this.poolName = poolName;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public int getMinPool() {
		return minPool;
	}

	public void setMinPool(int minPool) {
		this.minPool = minPool;
		if (pds != null) {
			pds.setMinPoolSize(this.minPool);
		}
	}

	public int getMaxPool() {
		return maxPool;
	}

	public void setMaxPool(int maxPool) {
		this.maxPool = maxPool;
		if (pds != null) {
			pds.setMaxPoolSize(this.maxPool);
		}
	}

	public int getInitPool() {
		return initPool;
	}

	public void setInitPool(int initPool) {
		this.initPool = initPool;
		if (pds != null) {
			pds.setInitialPoolSize(this.initPool);
		}
	}

	@Override
	public int getBusyPool() {
		try {
			return pds.getNumBusyConnections();
		} catch (SQLException e) {
			return pds.getMaxPoolSize();
		}
	}
}
